{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用 Python 实现 PLA："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "def pla(X, y):\n",
    "    \"\"\"\n",
    "    感知器学习算法实现\n",
    "    注意只能处理线性可分的数据集，输入线性不可分函数无法停止\n",
    "    args:\n",
    "        X - 训练数据集\n",
    "        y - 目标标签值\n",
    "    return:\n",
    "        W - 权重系数\n",
    "    \"\"\"\n",
    "    done = False\n",
    "    # 初始化权重系数\n",
    "    W = np.zeros(X.shape[1])\n",
    "    # 循环\n",
    "    while(done == False):\n",
    "        done = True\n",
    "        # 遍历训练数据集\n",
    "        for index in range(len(X)):\n",
    "            x = X[index]\n",
    "            # 判定是否与目标值不符\n",
    "            if x.dot(W) * y[index] <= 0:\n",
    "                done = False\n",
    "                # 修正权重系数\n",
    "                W = W + y[index] * x\n",
    "    return W"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def createTrainDatas(W, start, end, size = 10):\n",
    "    \"\"\"\n",
    "    创建线性可分的训练数据集\n",
    "    args:\n",
    "        W - 目标权重系数\n",
    "        start - 最小值\n",
    "        end - 最大值\n",
    "        size - 训练数据集大小\n",
    "    return:\n",
    "        X - 训练集特征值\n",
    "        y - 训练集目标值\n",
    "    \"\"\"\n",
    "    np.random.seed(42)\n",
    "    X = np.random.uniform(start, end, (size, W.shape[0]))\n",
    "    y = np.sign(X.dot(W))\n",
    "    return X, y\n",
    "\n",
    "def buildLine(W, start, end, size = 100):\n",
    "    \"\"\"\n",
    "    构建一条指定的二维直线\n",
    "    args:\n",
    "        W - 权重系数\n",
    "        start - 最小值\n",
    "        end - 最大值\n",
    "        size - 组成线的点的数量\n",
    "    return:\n",
    "        x0, - \n",
    "    \"\"\"\n",
    "    x0 = np.linspace(start, end, size)\n",
    "    if W[1] == 0:\n",
    "        x1 = np.zeros(100)\n",
    "    else:\n",
    "        x1 = -W[0] * x0 / W[1]\n",
    "    return x0, x1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "创建线性可分的训练数据集："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 坐标轴起始点\n",
    "start = -10\n",
    "# 坐标轴结束点\n",
    "end = 10\n",
    "# 目标权重系数\n",
    "W = np.array([5, 4])\n",
    "# 创建线性可分的训练数据集\n",
    "X, y = createTrainDatas(W, start, end)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "计算权重系数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([16.47819278, 14.08657577])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "W = pla(X, y)\n",
    "W"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可视化："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.rcParams['font.sans-serif'] = ['PingFang HK']  # 选择一个本地的支持中文的字体\n",
    "\n",
    "x1 = X[y>0][:, 0]\n",
    "y1 = X[y>0][:, 1]\n",
    "x2 = X[y<0][:, 0]\n",
    "y2 = X[y<0][:, 1]\n",
    "plt.title('PLA')\n",
    "p1 = plt.scatter(x1, y1, c='b', marker='o', s=20)\n",
    "p2 = plt.scatter(x2, y2, c='r', marker='o', s=20)\n",
    "x3, y3 = buildLine(W, start, end)\n",
    "plt.plot(x3, y3)\n",
    "plt.legend([p1, p2], [\"正1\", \"负1\"], loc=\"upper right\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "scikit-learn 实现："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([6.36350066, 6.3319098 ])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.linear_model import Perceptron\n",
    "\n",
    "# 初始化感知器\n",
    "clf = Perceptron(fit_intercept=False)\n",
    "# 拟合训练集\n",
    "clf.fit(X, y)\n",
    "# 权重系数\n",
    "W = clf.coef_[0]\n",
    "W"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可视化："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.rcParams['font.sans-serif'] = ['PingFang HK']  # 选择一个本地的支持中文的字体\n",
    "\n",
    "x1 = X[y>0][:, 0]\n",
    "y1 = X[y>0][:, 1]\n",
    "x2 = X[y<0][:, 0]\n",
    "y2 = X[y<0][:, 1]\n",
    "plt.title('PLA')\n",
    "p1 = plt.scatter(x1, y1, c='b', marker='o', s=20)\n",
    "p2 = plt.scatter(x2, y2, c='r', marker='o', s=20)\n",
    "x3, y3 = buildLine(W, start, end)\n",
    "plt.plot(x3, y3)\n",
    "plt.legend([p1, p2], [\"正1\", \"负1\"], loc=\"upper right\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "感知器学习算法演示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "def plaReturn(X, y):\n",
    "    \"\"\"\n",
    "    感知器学习算法实现\n",
    "    注意只能处理线性可分的数据集，输入线性不可分函数无法停止\n",
    "    args:\n",
    "        X - 训练数据集\n",
    "        y - 目标标签值\n",
    "    return:\n",
    "        Ws - 权重系数数组\n",
    "        errors - 错误数据下标数组\n",
    "    \"\"\"\n",
    "    done = False\n",
    "    # 初始化权重系数\n",
    "    W = np.zeros(X.shape[1])\n",
    "    Ws = np.array([W])\n",
    "    errors = []\n",
    "    # 循环\n",
    "    while(done == False):\n",
    "        done = True\n",
    "        # 遍历训练数据集\n",
    "        for index in range(len(X)):\n",
    "            x = X[index]\n",
    "            # 判定是否与目标值不符\n",
    "            if x.dot(W) * y[index] <= 0:\n",
    "                done = False\n",
    "                # 修正权重系数\n",
    "                W = W + y[index] * x\n",
    "                Ws = np.insert(Ws, len(Ws), values=W, axis=0)\n",
    "                errors.append(index)\n",
    "    return Ws, errors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "简单数据集："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "/* Put everything inside the global mpl namespace */\n",
       "/* global mpl */\n",
       "window.mpl = {};\n",
       "\n",
       "mpl.get_websocket_type = function () {\n",
       "    if (typeof WebSocket !== 'undefined') {\n",
       "        return WebSocket;\n",
       "    } else if (typeof MozWebSocket !== 'undefined') {\n",
       "        return MozWebSocket;\n",
       "    } else {\n",
       "        alert(\n",
       "            'Your browser does not have WebSocket support. ' +\n",
       "                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
       "                'Firefox 4 and 5 are also supported but you ' +\n",
       "                'have to enable WebSockets in about:config.'\n",
       "        );\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
       "    this.id = figure_id;\n",
       "\n",
       "    this.ws = websocket;\n",
       "\n",
       "    this.supports_binary = this.ws.binaryType !== undefined;\n",
       "\n",
       "    if (!this.supports_binary) {\n",
       "        var warnings = document.getElementById('mpl-warnings');\n",
       "        if (warnings) {\n",
       "            warnings.style.display = 'block';\n",
       "            warnings.textContent =\n",
       "                'This browser does not support binary websocket messages. ' +\n",
       "                'Performance may be slow.';\n",
       "        }\n",
       "    }\n",
       "\n",
       "    this.imageObj = new Image();\n",
       "\n",
       "    this.context = undefined;\n",
       "    this.message = undefined;\n",
       "    this.canvas = undefined;\n",
       "    this.rubberband_canvas = undefined;\n",
       "    this.rubberband_context = undefined;\n",
       "    this.format_dropdown = undefined;\n",
       "\n",
       "    this.image_mode = 'full';\n",
       "\n",
       "    this.root = document.createElement('div');\n",
       "    this.root.setAttribute('style', 'display: inline-block');\n",
       "    this._root_extra_style(this.root);\n",
       "\n",
       "    parent_element.appendChild(this.root);\n",
       "\n",
       "    this._init_header(this);\n",
       "    this._init_canvas(this);\n",
       "    this._init_toolbar(this);\n",
       "\n",
       "    var fig = this;\n",
       "\n",
       "    this.waiting = false;\n",
       "\n",
       "    this.ws.onopen = function () {\n",
       "        fig.send_message('supports_binary', { value: fig.supports_binary });\n",
       "        fig.send_message('send_image_mode', {});\n",
       "        if (fig.ratio !== 1) {\n",
       "            fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n",
       "        }\n",
       "        fig.send_message('refresh', {});\n",
       "    };\n",
       "\n",
       "    this.imageObj.onload = function () {\n",
       "        if (fig.image_mode === 'full') {\n",
       "            // Full images could contain transparency (where diff images\n",
       "            // almost always do), so we need to clear the canvas so that\n",
       "            // there is no ghosting.\n",
       "            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
       "        }\n",
       "        fig.context.drawImage(fig.imageObj, 0, 0);\n",
       "    };\n",
       "\n",
       "    this.imageObj.onunload = function () {\n",
       "        fig.ws.close();\n",
       "    };\n",
       "\n",
       "    this.ws.onmessage = this._make_on_message_function(this);\n",
       "\n",
       "    this.ondownload = ondownload;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._init_header = function () {\n",
       "    var titlebar = document.createElement('div');\n",
       "    titlebar.classList =\n",
       "        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
       "    var titletext = document.createElement('div');\n",
       "    titletext.classList = 'ui-dialog-title';\n",
       "    titletext.setAttribute(\n",
       "        'style',\n",
       "        'width: 100%; text-align: center; padding: 3px;'\n",
       "    );\n",
       "    titlebar.appendChild(titletext);\n",
       "    this.root.appendChild(titlebar);\n",
       "    this.header = titletext;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
       "\n",
       "mpl.figure.prototype._init_canvas = function () {\n",
       "    var fig = this;\n",
       "\n",
       "    var canvas_div = (this.canvas_div = document.createElement('div'));\n",
       "    canvas_div.setAttribute(\n",
       "        'style',\n",
       "        'border: 1px solid #ddd;' +\n",
       "            'box-sizing: content-box;' +\n",
       "            'clear: both;' +\n",
       "            'min-height: 1px;' +\n",
       "            'min-width: 1px;' +\n",
       "            'outline: 0;' +\n",
       "            'overflow: hidden;' +\n",
       "            'position: relative;' +\n",
       "            'resize: both;'\n",
       "    );\n",
       "\n",
       "    function on_keyboard_event_closure(name) {\n",
       "        return function (event) {\n",
       "            return fig.key_event(event, name);\n",
       "        };\n",
       "    }\n",
       "\n",
       "    canvas_div.addEventListener(\n",
       "        'keydown',\n",
       "        on_keyboard_event_closure('key_press')\n",
       "    );\n",
       "    canvas_div.addEventListener(\n",
       "        'keyup',\n",
       "        on_keyboard_event_closure('key_release')\n",
       "    );\n",
       "\n",
       "    this._canvas_extra_style(canvas_div);\n",
       "    this.root.appendChild(canvas_div);\n",
       "\n",
       "    var canvas = (this.canvas = document.createElement('canvas'));\n",
       "    canvas.classList.add('mpl-canvas');\n",
       "    canvas.setAttribute('style', 'box-sizing: content-box;');\n",
       "\n",
       "    this.context = canvas.getContext('2d');\n",
       "\n",
       "    var backingStore =\n",
       "        this.context.backingStorePixelRatio ||\n",
       "        this.context.webkitBackingStorePixelRatio ||\n",
       "        this.context.mozBackingStorePixelRatio ||\n",
       "        this.context.msBackingStorePixelRatio ||\n",
       "        this.context.oBackingStorePixelRatio ||\n",
       "        this.context.backingStorePixelRatio ||\n",
       "        1;\n",
       "\n",
       "    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
       "\n",
       "    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
       "        'canvas'\n",
       "    ));\n",
       "    rubberband_canvas.setAttribute(\n",
       "        'style',\n",
       "        'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
       "    );\n",
       "\n",
       "    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
       "    if (this.ResizeObserver === undefined) {\n",
       "        if (window.ResizeObserver !== undefined) {\n",
       "            this.ResizeObserver = window.ResizeObserver;\n",
       "        } else {\n",
       "            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
       "            this.ResizeObserver = obs.ResizeObserver;\n",
       "        }\n",
       "    }\n",
       "\n",
       "    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
       "        var nentries = entries.length;\n",
       "        for (var i = 0; i < nentries; i++) {\n",
       "            var entry = entries[i];\n",
       "            var width, height;\n",
       "            if (entry.contentBoxSize) {\n",
       "                if (entry.contentBoxSize instanceof Array) {\n",
       "                    // Chrome 84 implements new version of spec.\n",
       "                    width = entry.contentBoxSize[0].inlineSize;\n",
       "                    height = entry.contentBoxSize[0].blockSize;\n",
       "                } else {\n",
       "                    // Firefox implements old version of spec.\n",
       "                    width = entry.contentBoxSize.inlineSize;\n",
       "                    height = entry.contentBoxSize.blockSize;\n",
       "                }\n",
       "            } else {\n",
       "                // Chrome <84 implements even older version of spec.\n",
       "                width = entry.contentRect.width;\n",
       "                height = entry.contentRect.height;\n",
       "            }\n",
       "\n",
       "            // Keep the size of the canvas and rubber band canvas in sync with\n",
       "            // the canvas container.\n",
       "            if (entry.devicePixelContentBoxSize) {\n",
       "                // Chrome 84 implements new version of spec.\n",
       "                canvas.setAttribute(\n",
       "                    'width',\n",
       "                    entry.devicePixelContentBoxSize[0].inlineSize\n",
       "                );\n",
       "                canvas.setAttribute(\n",
       "                    'height',\n",
       "                    entry.devicePixelContentBoxSize[0].blockSize\n",
       "                );\n",
       "            } else {\n",
       "                canvas.setAttribute('width', width * fig.ratio);\n",
       "                canvas.setAttribute('height', height * fig.ratio);\n",
       "            }\n",
       "            canvas.setAttribute(\n",
       "                'style',\n",
       "                'width: ' + width + 'px; height: ' + height + 'px;'\n",
       "            );\n",
       "\n",
       "            rubberband_canvas.setAttribute('width', width);\n",
       "            rubberband_canvas.setAttribute('height', height);\n",
       "\n",
       "            // And update the size in Python. We ignore the initial 0/0 size\n",
       "            // that occurs as the element is placed into the DOM, which should\n",
       "            // otherwise not happen due to the minimum size styling.\n",
       "            if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
       "                fig.request_resize(width, height);\n",
       "            }\n",
       "        }\n",
       "    });\n",
       "    this.resizeObserverInstance.observe(canvas_div);\n",
       "\n",
       "    function on_mouse_event_closure(name) {\n",
       "        return function (event) {\n",
       "            return fig.mouse_event(event, name);\n",
       "        };\n",
       "    }\n",
       "\n",
       "    rubberband_canvas.addEventListener(\n",
       "        'mousedown',\n",
       "        on_mouse_event_closure('button_press')\n",
       "    );\n",
       "    rubberband_canvas.addEventListener(\n",
       "        'mouseup',\n",
       "        on_mouse_event_closure('button_release')\n",
       "    );\n",
       "    // Throttle sequential mouse events to 1 every 20ms.\n",
       "    rubberband_canvas.addEventListener(\n",
       "        'mousemove',\n",
       "        on_mouse_event_closure('motion_notify')\n",
       "    );\n",
       "\n",
       "    rubberband_canvas.addEventListener(\n",
       "        'mouseenter',\n",
       "        on_mouse_event_closure('figure_enter')\n",
       "    );\n",
       "    rubberband_canvas.addEventListener(\n",
       "        'mouseleave',\n",
       "        on_mouse_event_closure('figure_leave')\n",
       "    );\n",
       "\n",
       "    canvas_div.addEventListener('wheel', function (event) {\n",
       "        if (event.deltaY < 0) {\n",
       "            event.step = 1;\n",
       "        } else {\n",
       "            event.step = -1;\n",
       "        }\n",
       "        on_mouse_event_closure('scroll')(event);\n",
       "    });\n",
       "\n",
       "    canvas_div.appendChild(canvas);\n",
       "    canvas_div.appendChild(rubberband_canvas);\n",
       "\n",
       "    this.rubberband_context = rubberband_canvas.getContext('2d');\n",
       "    this.rubberband_context.strokeStyle = '#000000';\n",
       "\n",
       "    this._resize_canvas = function (width, height, forward) {\n",
       "        if (forward) {\n",
       "            canvas_div.style.width = width + 'px';\n",
       "            canvas_div.style.height = height + 'px';\n",
       "        }\n",
       "    };\n",
       "\n",
       "    // Disable right mouse context menu.\n",
       "    this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
       "        event.preventDefault();\n",
       "        return false;\n",
       "    });\n",
       "\n",
       "    function set_focus() {\n",
       "        canvas.focus();\n",
       "        canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    window.setTimeout(set_focus, 100);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function () {\n",
       "    var fig = this;\n",
       "\n",
       "    var toolbar = document.createElement('div');\n",
       "    toolbar.classList = 'mpl-toolbar';\n",
       "    this.root.appendChild(toolbar);\n",
       "\n",
       "    function on_click_closure(name) {\n",
       "        return function (_event) {\n",
       "            return fig.toolbar_button_onclick(name);\n",
       "        };\n",
       "    }\n",
       "\n",
       "    function on_mouseover_closure(tooltip) {\n",
       "        return function (event) {\n",
       "            if (!event.currentTarget.disabled) {\n",
       "                return fig.toolbar_button_onmouseover(tooltip);\n",
       "            }\n",
       "        };\n",
       "    }\n",
       "\n",
       "    fig.buttons = {};\n",
       "    var buttonGroup = document.createElement('div');\n",
       "    buttonGroup.classList = 'mpl-button-group';\n",
       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) {\n",
       "            /* Instead of a spacer, we start a new button group. */\n",
       "            if (buttonGroup.hasChildNodes()) {\n",
       "                toolbar.appendChild(buttonGroup);\n",
       "            }\n",
       "            buttonGroup = document.createElement('div');\n",
       "            buttonGroup.classList = 'mpl-button-group';\n",
       "            continue;\n",
       "        }\n",
       "\n",
       "        var button = (fig.buttons[name] = document.createElement('button'));\n",
       "        button.classList = 'mpl-widget';\n",
       "        button.setAttribute('role', 'button');\n",
       "        button.setAttribute('aria-disabled', 'false');\n",
       "        button.addEventListener('click', on_click_closure(method_name));\n",
       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
       "\n",
       "        var icon_img = document.createElement('img');\n",
       "        icon_img.src = '_images/' + image + '.png';\n",
       "        icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
       "        icon_img.alt = tooltip;\n",
       "        button.appendChild(icon_img);\n",
       "\n",
       "        buttonGroup.appendChild(button);\n",
       "    }\n",
       "\n",
       "    if (buttonGroup.hasChildNodes()) {\n",
       "        toolbar.appendChild(buttonGroup);\n",
       "    }\n",
       "\n",
       "    var fmt_picker = document.createElement('select');\n",
       "    fmt_picker.classList = 'mpl-widget';\n",
       "    toolbar.appendChild(fmt_picker);\n",
       "    this.format_dropdown = fmt_picker;\n",
       "\n",
       "    for (var ind in mpl.extensions) {\n",
       "        var fmt = mpl.extensions[ind];\n",
       "        var option = document.createElement('option');\n",
       "        option.selected = fmt === mpl.default_extension;\n",
       "        option.innerHTML = fmt;\n",
       "        fmt_picker.appendChild(option);\n",
       "    }\n",
       "\n",
       "    var status_bar = document.createElement('span');\n",
       "    status_bar.classList = 'mpl-message';\n",
       "    toolbar.appendChild(status_bar);\n",
       "    this.message = status_bar;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
       "    // which will in turn request a refresh of the image.\n",
       "    this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.send_message = function (type, properties) {\n",
       "    properties['type'] = type;\n",
       "    properties['figure_id'] = this.id;\n",
       "    this.ws.send(JSON.stringify(properties));\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.send_draw_message = function () {\n",
       "    if (!this.waiting) {\n",
       "        this.waiting = true;\n",
       "        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
       "    var format_dropdown = fig.format_dropdown;\n",
       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
       "    fig.ondownload(fig, format);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
       "    var size = msg['size'];\n",
       "    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
       "        fig._resize_canvas(size[0], size[1], msg['forward']);\n",
       "        fig.send_message('refresh', {});\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
       "    var x0 = msg['x0'] / fig.ratio;\n",
       "    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
       "    var x1 = msg['x1'] / fig.ratio;\n",
       "    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
       "    x0 = Math.floor(x0) + 0.5;\n",
       "    y0 = Math.floor(y0) + 0.5;\n",
       "    x1 = Math.floor(x1) + 0.5;\n",
       "    y1 = Math.floor(y1) + 0.5;\n",
       "    var min_x = Math.min(x0, x1);\n",
       "    var min_y = Math.min(y0, y1);\n",
       "    var width = Math.abs(x1 - x0);\n",
       "    var height = Math.abs(y1 - y0);\n",
       "\n",
       "    fig.rubberband_context.clearRect(\n",
       "        0,\n",
       "        0,\n",
       "        fig.canvas.width / fig.ratio,\n",
       "        fig.canvas.height / fig.ratio\n",
       "    );\n",
       "\n",
       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
       "    // Updates the figure title.\n",
       "    fig.header.textContent = msg['label'];\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
       "    var cursor = msg['cursor'];\n",
       "    switch (cursor) {\n",
       "        case 0:\n",
       "            cursor = 'pointer';\n",
       "            break;\n",
       "        case 1:\n",
       "            cursor = 'default';\n",
       "            break;\n",
       "        case 2:\n",
       "            cursor = 'crosshair';\n",
       "            break;\n",
       "        case 3:\n",
       "            cursor = 'move';\n",
       "            break;\n",
       "    }\n",
       "    fig.rubberband_canvas.style.cursor = cursor;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
       "    fig.message.textContent = msg['message'];\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
       "    // Request the server to send over a new figure.\n",
       "    fig.send_draw_message();\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
       "    fig.image_mode = msg['mode'];\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
       "    for (var key in msg) {\n",
       "        if (!(key in fig.buttons)) {\n",
       "            continue;\n",
       "        }\n",
       "        fig.buttons[key].disabled = !msg[key];\n",
       "        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
       "    if (msg['mode'] === 'PAN') {\n",
       "        fig.buttons['Pan'].classList.add('active');\n",
       "        fig.buttons['Zoom'].classList.remove('active');\n",
       "    } else if (msg['mode'] === 'ZOOM') {\n",
       "        fig.buttons['Pan'].classList.remove('active');\n",
       "        fig.buttons['Zoom'].classList.add('active');\n",
       "    } else {\n",
       "        fig.buttons['Pan'].classList.remove('active');\n",
       "        fig.buttons['Zoom'].classList.remove('active');\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function () {\n",
       "    // Called whenever the canvas gets updated.\n",
       "    this.send_message('ack', {});\n",
       "};\n",
       "\n",
       "// A function to construct a web socket function for onmessage handling.\n",
       "// Called in the figure constructor.\n",
       "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
       "    return function socket_on_message(evt) {\n",
       "        if (evt.data instanceof Blob) {\n",
       "            /* FIXME: We get \"Resource interpreted as Image but\n",
       "             * transferred with MIME type text/plain:\" errors on\n",
       "             * Chrome.  But how to set the MIME type?  It doesn't seem\n",
       "             * to be part of the websocket stream */\n",
       "            evt.data.type = 'image/png';\n",
       "\n",
       "            /* Free the memory for the previous frames */\n",
       "            if (fig.imageObj.src) {\n",
       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
       "                    fig.imageObj.src\n",
       "                );\n",
       "            }\n",
       "\n",
       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
       "                evt.data\n",
       "            );\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        } else if (\n",
       "            typeof evt.data === 'string' &&\n",
       "            evt.data.slice(0, 21) === 'data:image/png;base64'\n",
       "        ) {\n",
       "            fig.imageObj.src = evt.data;\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        var msg = JSON.parse(evt.data);\n",
       "        var msg_type = msg['type'];\n",
       "\n",
       "        // Call the  \"handle_{type}\" callback, which takes\n",
       "        // the figure and JSON message as its only arguments.\n",
       "        try {\n",
       "            var callback = fig['handle_' + msg_type];\n",
       "        } catch (e) {\n",
       "            console.log(\n",
       "                \"No handler for the '\" + msg_type + \"' message type: \",\n",
       "                msg\n",
       "            );\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        if (callback) {\n",
       "            try {\n",
       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
       "                callback(fig, msg);\n",
       "            } catch (e) {\n",
       "                console.log(\n",
       "                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
       "                    e,\n",
       "                    e.stack,\n",
       "                    msg\n",
       "                );\n",
       "            }\n",
       "        }\n",
       "    };\n",
       "};\n",
       "\n",
       "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
       "mpl.findpos = function (e) {\n",
       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
       "    var targ;\n",
       "    if (!e) {\n",
       "        e = window.event;\n",
       "    }\n",
       "    if (e.target) {\n",
       "        targ = e.target;\n",
       "    } else if (e.srcElement) {\n",
       "        targ = e.srcElement;\n",
       "    }\n",
       "    if (targ.nodeType === 3) {\n",
       "        // defeat Safari bug\n",
       "        targ = targ.parentNode;\n",
       "    }\n",
       "\n",
       "    // pageX,Y are the mouse positions relative to the document\n",
       "    var boundingRect = targ.getBoundingClientRect();\n",
       "    var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
       "    var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
       "\n",
       "    return { x: x, y: y };\n",
       "};\n",
       "\n",
       "/*\n",
       " * return a copy of an object with only non-object keys\n",
       " * we need this to avoid circular references\n",
       " * http://stackoverflow.com/a/24161582/3208463\n",
       " */\n",
       "function simpleKeys(original) {\n",
       "    return Object.keys(original).reduce(function (obj, key) {\n",
       "        if (typeof original[key] !== 'object') {\n",
       "            obj[key] = original[key];\n",
       "        }\n",
       "        return obj;\n",
       "    }, {});\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.mouse_event = function (event, name) {\n",
       "    var canvas_pos = mpl.findpos(event);\n",
       "\n",
       "    if (name === 'button_press') {\n",
       "        this.canvas.focus();\n",
       "        this.canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    var x = canvas_pos.x * this.ratio;\n",
       "    var y = canvas_pos.y * this.ratio;\n",
       "\n",
       "    this.send_message(name, {\n",
       "        x: x,\n",
       "        y: y,\n",
       "        button: event.button,\n",
       "        step: event.step,\n",
       "        guiEvent: simpleKeys(event),\n",
       "    });\n",
       "\n",
       "    /* This prevents the web browser from automatically changing to\n",
       "     * the text insertion cursor when the button is pressed.  We want\n",
       "     * to control all of the cursor setting manually through the\n",
       "     * 'cursor' event from matplotlib */\n",
       "    event.preventDefault();\n",
       "    return false;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
       "    // Handle any extra behaviour associated with a key event\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.key_event = function (event, name) {\n",
       "    // Prevent repeat events\n",
       "    if (name === 'key_press') {\n",
       "        if (event.which === this._key) {\n",
       "            return;\n",
       "        } else {\n",
       "            this._key = event.which;\n",
       "        }\n",
       "    }\n",
       "    if (name === 'key_release') {\n",
       "        this._key = null;\n",
       "    }\n",
       "\n",
       "    var value = '';\n",
       "    if (event.ctrlKey && event.which !== 17) {\n",
       "        value += 'ctrl+';\n",
       "    }\n",
       "    if (event.altKey && event.which !== 18) {\n",
       "        value += 'alt+';\n",
       "    }\n",
       "    if (event.shiftKey && event.which !== 16) {\n",
       "        value += 'shift+';\n",
       "    }\n",
       "\n",
       "    value += 'k';\n",
       "    value += event.which.toString();\n",
       "\n",
       "    this._key_event_extra(event, name);\n",
       "\n",
       "    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
       "    return false;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
       "    if (name === 'download') {\n",
       "        this.handle_save(this, null);\n",
       "    } else {\n",
       "        this.send_message('toolbar_button', { name: name });\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
       "    this.message.textContent = tooltip;\n",
       "};\n",
       "\n",
       "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
       "// prettier-ignore\n",
       "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
       "\n",
       "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
       "\n",
       "mpl.default_extension = \"png\";/* global mpl */\n",
       "\n",
       "var comm_websocket_adapter = function (comm) {\n",
       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
       "    // object with the appropriate methods. Currently this is a non binary\n",
       "    // socket, so there is still some room for performance tuning.\n",
       "    var ws = {};\n",
       "\n",
       "    ws.close = function () {\n",
       "        comm.close();\n",
       "    };\n",
       "    ws.send = function (m) {\n",
       "        //console.log('sending', m);\n",
       "        comm.send(m);\n",
       "    };\n",
       "    // Register the callback with on_msg.\n",
       "    comm.on_msg(function (msg) {\n",
       "        //console.log('receiving', msg['content']['data'], msg);\n",
       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
       "        ws.onmessage(msg['content']['data']);\n",
       "    });\n",
       "    return ws;\n",
       "};\n",
       "\n",
       "mpl.mpl_figure_comm = function (comm, msg) {\n",
       "    // This is the function which gets called when the mpl process\n",
       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
       "\n",
       "    var id = msg.content.data.id;\n",
       "    // Get hold of the div created by the display call when the Comm\n",
       "    // socket was opened in Python.\n",
       "    var element = document.getElementById(id);\n",
       "    var ws_proxy = comm_websocket_adapter(comm);\n",
       "\n",
       "    function ondownload(figure, _format) {\n",
       "        window.open(figure.canvas.toDataURL());\n",
       "    }\n",
       "\n",
       "    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
       "\n",
       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
       "    // web socket which is closed, not our websocket->open comm proxy.\n",
       "    ws_proxy.onopen();\n",
       "\n",
       "    fig.parent_element = element;\n",
       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
       "    if (!fig.cell_info) {\n",
       "        console.error('Failed to find cell for figure', id, fig);\n",
       "        return;\n",
       "    }\n",
       "    fig.cell_info[0].output_area.element.on(\n",
       "        'cleared',\n",
       "        { fig: fig },\n",
       "        fig._remove_fig_handler\n",
       "    );\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
       "    var width = fig.canvas.width / fig.ratio;\n",
       "    fig.cell_info[0].output_area.element.off(\n",
       "        'cleared',\n",
       "        fig._remove_fig_handler\n",
       "    );\n",
       "    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
       "\n",
       "    // Update the output cell to use the data from the current canvas.\n",
       "    fig.push_to_output();\n",
       "    var dataURL = fig.canvas.toDataURL();\n",
       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
       "    // the notebook keyboard shortcuts fail.\n",
       "    IPython.keyboard_manager.enable();\n",
       "    fig.parent_element.innerHTML =\n",
       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
       "    fig.close_ws(fig, msg);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
       "    fig.send_message('closing', msg);\n",
       "    // fig.ws.close()\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
       "    // Turn the data on the canvas into data in the output cell.\n",
       "    var width = this.canvas.width / this.ratio;\n",
       "    var dataURL = this.canvas.toDataURL();\n",
       "    this.cell_info[1]['text/html'] =\n",
       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function () {\n",
       "    // Tell IPython that the notebook contents must change.\n",
       "    IPython.notebook.set_dirty(true);\n",
       "    this.send_message('ack', {});\n",
       "    var fig = this;\n",
       "    // Wait a second, then push the new image to the DOM so\n",
       "    // that it is saved nicely (might be nice to debounce this).\n",
       "    setTimeout(function () {\n",
       "        fig.push_to_output();\n",
       "    }, 1000);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function () {\n",
       "    var fig = this;\n",
       "\n",
       "    var toolbar = document.createElement('div');\n",
       "    toolbar.classList = 'btn-toolbar';\n",
       "    this.root.appendChild(toolbar);\n",
       "\n",
       "    function on_click_closure(name) {\n",
       "        return function (_event) {\n",
       "            return fig.toolbar_button_onclick(name);\n",
       "        };\n",
       "    }\n",
       "\n",
       "    function on_mouseover_closure(tooltip) {\n",
       "        return function (event) {\n",
       "            if (!event.currentTarget.disabled) {\n",
       "                return fig.toolbar_button_onmouseover(tooltip);\n",
       "            }\n",
       "        };\n",
       "    }\n",
       "\n",
       "    fig.buttons = {};\n",
       "    var buttonGroup = document.createElement('div');\n",
       "    buttonGroup.classList = 'btn-group';\n",
       "    var button;\n",
       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) {\n",
       "            /* Instead of a spacer, we start a new button group. */\n",
       "            if (buttonGroup.hasChildNodes()) {\n",
       "                toolbar.appendChild(buttonGroup);\n",
       "            }\n",
       "            buttonGroup = document.createElement('div');\n",
       "            buttonGroup.classList = 'btn-group';\n",
       "            continue;\n",
       "        }\n",
       "\n",
       "        button = fig.buttons[name] = document.createElement('button');\n",
       "        button.classList = 'btn btn-default';\n",
       "        button.href = '#';\n",
       "        button.title = name;\n",
       "        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n",
       "        button.addEventListener('click', on_click_closure(method_name));\n",
       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
       "        buttonGroup.appendChild(button);\n",
       "    }\n",
       "\n",
       "    if (buttonGroup.hasChildNodes()) {\n",
       "        toolbar.appendChild(buttonGroup);\n",
       "    }\n",
       "\n",
       "    // Add the status bar.\n",
       "    var status_bar = document.createElement('span');\n",
       "    status_bar.classList = 'mpl-message pull-right';\n",
       "    toolbar.appendChild(status_bar);\n",
       "    this.message = status_bar;\n",
       "\n",
       "    // Add the close button to the window.\n",
       "    var buttongrp = document.createElement('div');\n",
       "    buttongrp.classList = 'btn-group inline pull-right';\n",
       "    button = document.createElement('button');\n",
       "    button.classList = 'btn btn-mini btn-primary';\n",
       "    button.href = '#';\n",
       "    button.title = 'Stop Interaction';\n",
       "    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n",
       "    button.addEventListener('click', function (_evt) {\n",
       "        fig.handle_close(fig, {});\n",
       "    });\n",
       "    button.addEventListener(\n",
       "        'mouseover',\n",
       "        on_mouseover_closure('Stop Interaction')\n",
       "    );\n",
       "    buttongrp.appendChild(button);\n",
       "    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
       "    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
       "    var fig = event.data.fig;\n",
       "    if (event.target !== this) {\n",
       "        // Ignore bubbled events from children.\n",
       "        return;\n",
       "    }\n",
       "    fig.close_ws(fig, {});\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function (el) {\n",
       "    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
       "    // this is important to make the div 'focusable\n",
       "    el.setAttribute('tabindex', 0);\n",
       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
       "    // off when our div gets focus\n",
       "\n",
       "    // location in version 3\n",
       "    if (IPython.notebook.keyboard_manager) {\n",
       "        IPython.notebook.keyboard_manager.register_events(el);\n",
       "    } else {\n",
       "        // location in version 2\n",
       "        IPython.keyboard_manager.register_events(el);\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
       "    var manager = IPython.notebook.keyboard_manager;\n",
       "    if (!manager) {\n",
       "        manager = IPython.keyboard_manager;\n",
       "    }\n",
       "\n",
       "    // Check for shift+enter\n",
       "    if (event.shiftKey && event.which === 13) {\n",
       "        this.canvas_div.blur();\n",
       "        // select the cell after this one\n",
       "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
       "        IPython.notebook.select(index + 1);\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
       "    fig.ondownload(fig, null);\n",
       "};\n",
       "\n",
       "mpl.find_output_cell = function (html_output) {\n",
       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
       "    // IPython event is triggered only after the cells have been serialised, which for\n",
       "    // our purposes (turning an active figure into a static one), is too late.\n",
       "    var cells = IPython.notebook.get_cells();\n",
       "    var ncells = cells.length;\n",
       "    for (var i = 0; i < ncells; i++) {\n",
       "        var cell = cells[i];\n",
       "        if (cell.cell_type === 'code') {\n",
       "            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
       "                var data = cell.output_area.outputs[j];\n",
       "                if (data.data) {\n",
       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
       "                    data = data.data;\n",
       "                }\n",
       "                if (data['text/html'] === html_output) {\n",
       "                    return [cell, data, j];\n",
       "                }\n",
       "            }\n",
       "        }\n",
       "    }\n",
       "};\n",
       "\n",
       "// Register the function which deals with the matplotlib target/channel.\n",
       "// The kernel may be null if the page has been refreshed.\n",
       "if (IPython.notebook.kernel !== null) {\n",
       "    IPython.notebook.kernel.comm_manager.register_target(\n",
       "        'matplotlib',\n",
       "        mpl.mpl_figure_comm\n",
       "    );\n",
       "}\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<img src=\"\" width=\"640\">"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.animation as animation\n",
    "\n",
    "plt.rcParams['font.sans-serif'] = ['PingFang HK']  # 选择一个本地的支持中文的字体\n",
    "\n",
    "%matplotlib notebook\n",
    "\n",
    "start = -10\n",
    "end = 10\n",
    "W = np.array([5, 4])\n",
    "X, y = createTrainDatas(W, start, end)\n",
    "x1 = X[y>0][:, 0]\n",
    "y1 = X[y>0][:, 1]\n",
    "x2 = X[y<0][:, 0]\n",
    "y2 = X[y<0][:, 1]\n",
    "\n",
    "Ws, errors = plaReturn(X, y)\n",
    "fig = plt.figure()\n",
    "plt.title('PLA')\n",
    "plt.scatter(x1, y1, c='b', marker='o', s=20)\n",
    "plt.scatter(x2, y2, c='r', marker='o', s=20)\n",
    "line, = plt.plot(0, 0, markersize=0, label='W')\n",
    "error, = plt.plot(0, 0, c='k', marker='x', markersize=0)\n",
    "\n",
    "def update(i):\n",
    "    x3, y3 = buildLine(Ws[i], start, end)\n",
    "    line.set_data(x3, y3)\n",
    "    line.set_label(\"第%d次更新W\"%(i + 1))\n",
    "    if i < len(errors) :\n",
    "        error.set_markersize(14)\n",
    "        error.set_data(X[errors[i]][0], X[errors[i]][1])\n",
    "    else:\n",
    "        error.set_markersize(0)\n",
    "        error.set_data(0, 0)\n",
    "    plt.legend(loc=\"upper right\")\n",
    "    return line, error,\n",
    "\n",
    "ani = animation.FuncAnimation(fig, update, range(0, len(Ws)), interval=1000, blit=True, repeat=False)\n",
    "ani.save('pla_simple.gif')\n",
    "plt.legend(loc=\"upper right\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "复杂数据集："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "/* Put everything inside the global mpl namespace */\n",
       "/* global mpl */\n",
       "window.mpl = {};\n",
       "\n",
       "mpl.get_websocket_type = function () {\n",
       "    if (typeof WebSocket !== 'undefined') {\n",
       "        return WebSocket;\n",
       "    } else if (typeof MozWebSocket !== 'undefined') {\n",
       "        return MozWebSocket;\n",
       "    } else {\n",
       "        alert(\n",
       "            'Your browser does not have WebSocket support. ' +\n",
       "                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
       "                'Firefox 4 and 5 are also supported but you ' +\n",
       "                'have to enable WebSockets in about:config.'\n",
       "        );\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
       "    this.id = figure_id;\n",
       "\n",
       "    this.ws = websocket;\n",
       "\n",
       "    this.supports_binary = this.ws.binaryType !== undefined;\n",
       "\n",
       "    if (!this.supports_binary) {\n",
       "        var warnings = document.getElementById('mpl-warnings');\n",
       "        if (warnings) {\n",
       "            warnings.style.display = 'block';\n",
       "            warnings.textContent =\n",
       "                'This browser does not support binary websocket messages. ' +\n",
       "                'Performance may be slow.';\n",
       "        }\n",
       "    }\n",
       "\n",
       "    this.imageObj = new Image();\n",
       "\n",
       "    this.context = undefined;\n",
       "    this.message = undefined;\n",
       "    this.canvas = undefined;\n",
       "    this.rubberband_canvas = undefined;\n",
       "    this.rubberband_context = undefined;\n",
       "    this.format_dropdown = undefined;\n",
       "\n",
       "    this.image_mode = 'full';\n",
       "\n",
       "    this.root = document.createElement('div');\n",
       "    this.root.setAttribute('style', 'display: inline-block');\n",
       "    this._root_extra_style(this.root);\n",
       "\n",
       "    parent_element.appendChild(this.root);\n",
       "\n",
       "    this._init_header(this);\n",
       "    this._init_canvas(this);\n",
       "    this._init_toolbar(this);\n",
       "\n",
       "    var fig = this;\n",
       "\n",
       "    this.waiting = false;\n",
       "\n",
       "    this.ws.onopen = function () {\n",
       "        fig.send_message('supports_binary', { value: fig.supports_binary });\n",
       "        fig.send_message('send_image_mode', {});\n",
       "        if (fig.ratio !== 1) {\n",
       "            fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n",
       "        }\n",
       "        fig.send_message('refresh', {});\n",
       "    };\n",
       "\n",
       "    this.imageObj.onload = function () {\n",
       "        if (fig.image_mode === 'full') {\n",
       "            // Full images could contain transparency (where diff images\n",
       "            // almost always do), so we need to clear the canvas so that\n",
       "            // there is no ghosting.\n",
       "            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
       "        }\n",
       "        fig.context.drawImage(fig.imageObj, 0, 0);\n",
       "    };\n",
       "\n",
       "    this.imageObj.onunload = function () {\n",
       "        fig.ws.close();\n",
       "    };\n",
       "\n",
       "    this.ws.onmessage = this._make_on_message_function(this);\n",
       "\n",
       "    this.ondownload = ondownload;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._init_header = function () {\n",
       "    var titlebar = document.createElement('div');\n",
       "    titlebar.classList =\n",
       "        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
       "    var titletext = document.createElement('div');\n",
       "    titletext.classList = 'ui-dialog-title';\n",
       "    titletext.setAttribute(\n",
       "        'style',\n",
       "        'width: 100%; text-align: center; padding: 3px;'\n",
       "    );\n",
       "    titlebar.appendChild(titletext);\n",
       "    this.root.appendChild(titlebar);\n",
       "    this.header = titletext;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
       "\n",
       "mpl.figure.prototype._init_canvas = function () {\n",
       "    var fig = this;\n",
       "\n",
       "    var canvas_div = (this.canvas_div = document.createElement('div'));\n",
       "    canvas_div.setAttribute(\n",
       "        'style',\n",
       "        'border: 1px solid #ddd;' +\n",
       "            'box-sizing: content-box;' +\n",
       "            'clear: both;' +\n",
       "            'min-height: 1px;' +\n",
       "            'min-width: 1px;' +\n",
       "            'outline: 0;' +\n",
       "            'overflow: hidden;' +\n",
       "            'position: relative;' +\n",
       "            'resize: both;'\n",
       "    );\n",
       "\n",
       "    function on_keyboard_event_closure(name) {\n",
       "        return function (event) {\n",
       "            return fig.key_event(event, name);\n",
       "        };\n",
       "    }\n",
       "\n",
       "    canvas_div.addEventListener(\n",
       "        'keydown',\n",
       "        on_keyboard_event_closure('key_press')\n",
       "    );\n",
       "    canvas_div.addEventListener(\n",
       "        'keyup',\n",
       "        on_keyboard_event_closure('key_release')\n",
       "    );\n",
       "\n",
       "    this._canvas_extra_style(canvas_div);\n",
       "    this.root.appendChild(canvas_div);\n",
       "\n",
       "    var canvas = (this.canvas = document.createElement('canvas'));\n",
       "    canvas.classList.add('mpl-canvas');\n",
       "    canvas.setAttribute('style', 'box-sizing: content-box;');\n",
       "\n",
       "    this.context = canvas.getContext('2d');\n",
       "\n",
       "    var backingStore =\n",
       "        this.context.backingStorePixelRatio ||\n",
       "        this.context.webkitBackingStorePixelRatio ||\n",
       "        this.context.mozBackingStorePixelRatio ||\n",
       "        this.context.msBackingStorePixelRatio ||\n",
       "        this.context.oBackingStorePixelRatio ||\n",
       "        this.context.backingStorePixelRatio ||\n",
       "        1;\n",
       "\n",
       "    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
       "\n",
       "    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
       "        'canvas'\n",
       "    ));\n",
       "    rubberband_canvas.setAttribute(\n",
       "        'style',\n",
       "        'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
       "    );\n",
       "\n",
       "    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
       "    if (this.ResizeObserver === undefined) {\n",
       "        if (window.ResizeObserver !== undefined) {\n",
       "            this.ResizeObserver = window.ResizeObserver;\n",
       "        } else {\n",
       "            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
       "            this.ResizeObserver = obs.ResizeObserver;\n",
       "        }\n",
       "    }\n",
       "\n",
       "    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
       "        var nentries = entries.length;\n",
       "        for (var i = 0; i < nentries; i++) {\n",
       "            var entry = entries[i];\n",
       "            var width, height;\n",
       "            if (entry.contentBoxSize) {\n",
       "                if (entry.contentBoxSize instanceof Array) {\n",
       "                    // Chrome 84 implements new version of spec.\n",
       "                    width = entry.contentBoxSize[0].inlineSize;\n",
       "                    height = entry.contentBoxSize[0].blockSize;\n",
       "                } else {\n",
       "                    // Firefox implements old version of spec.\n",
       "                    width = entry.contentBoxSize.inlineSize;\n",
       "                    height = entry.contentBoxSize.blockSize;\n",
       "                }\n",
       "            } else {\n",
       "                // Chrome <84 implements even older version of spec.\n",
       "                width = entry.contentRect.width;\n",
       "                height = entry.contentRect.height;\n",
       "            }\n",
       "\n",
       "            // Keep the size of the canvas and rubber band canvas in sync with\n",
       "            // the canvas container.\n",
       "            if (entry.devicePixelContentBoxSize) {\n",
       "                // Chrome 84 implements new version of spec.\n",
       "                canvas.setAttribute(\n",
       "                    'width',\n",
       "                    entry.devicePixelContentBoxSize[0].inlineSize\n",
       "                );\n",
       "                canvas.setAttribute(\n",
       "                    'height',\n",
       "                    entry.devicePixelContentBoxSize[0].blockSize\n",
       "                );\n",
       "            } else {\n",
       "                canvas.setAttribute('width', width * fig.ratio);\n",
       "                canvas.setAttribute('height', height * fig.ratio);\n",
       "            }\n",
       "            canvas.setAttribute(\n",
       "                'style',\n",
       "                'width: ' + width + 'px; height: ' + height + 'px;'\n",
       "            );\n",
       "\n",
       "            rubberband_canvas.setAttribute('width', width);\n",
       "            rubberband_canvas.setAttribute('height', height);\n",
       "\n",
       "            // And update the size in Python. We ignore the initial 0/0 size\n",
       "            // that occurs as the element is placed into the DOM, which should\n",
       "            // otherwise not happen due to the minimum size styling.\n",
       "            if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
       "                fig.request_resize(width, height);\n",
       "            }\n",
       "        }\n",
       "    });\n",
       "    this.resizeObserverInstance.observe(canvas_div);\n",
       "\n",
       "    function on_mouse_event_closure(name) {\n",
       "        return function (event) {\n",
       "            return fig.mouse_event(event, name);\n",
       "        };\n",
       "    }\n",
       "\n",
       "    rubberband_canvas.addEventListener(\n",
       "        'mousedown',\n",
       "        on_mouse_event_closure('button_press')\n",
       "    );\n",
       "    rubberband_canvas.addEventListener(\n",
       "        'mouseup',\n",
       "        on_mouse_event_closure('button_release')\n",
       "    );\n",
       "    // Throttle sequential mouse events to 1 every 20ms.\n",
       "    rubberband_canvas.addEventListener(\n",
       "        'mousemove',\n",
       "        on_mouse_event_closure('motion_notify')\n",
       "    );\n",
       "\n",
       "    rubberband_canvas.addEventListener(\n",
       "        'mouseenter',\n",
       "        on_mouse_event_closure('figure_enter')\n",
       "    );\n",
       "    rubberband_canvas.addEventListener(\n",
       "        'mouseleave',\n",
       "        on_mouse_event_closure('figure_leave')\n",
       "    );\n",
       "\n",
       "    canvas_div.addEventListener('wheel', function (event) {\n",
       "        if (event.deltaY < 0) {\n",
       "            event.step = 1;\n",
       "        } else {\n",
       "            event.step = -1;\n",
       "        }\n",
       "        on_mouse_event_closure('scroll')(event);\n",
       "    });\n",
       "\n",
       "    canvas_div.appendChild(canvas);\n",
       "    canvas_div.appendChild(rubberband_canvas);\n",
       "\n",
       "    this.rubberband_context = rubberband_canvas.getContext('2d');\n",
       "    this.rubberband_context.strokeStyle = '#000000';\n",
       "\n",
       "    this._resize_canvas = function (width, height, forward) {\n",
       "        if (forward) {\n",
       "            canvas_div.style.width = width + 'px';\n",
       "            canvas_div.style.height = height + 'px';\n",
       "        }\n",
       "    };\n",
       "\n",
       "    // Disable right mouse context menu.\n",
       "    this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
       "        event.preventDefault();\n",
       "        return false;\n",
       "    });\n",
       "\n",
       "    function set_focus() {\n",
       "        canvas.focus();\n",
       "        canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    window.setTimeout(set_focus, 100);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function () {\n",
       "    var fig = this;\n",
       "\n",
       "    var toolbar = document.createElement('div');\n",
       "    toolbar.classList = 'mpl-toolbar';\n",
       "    this.root.appendChild(toolbar);\n",
       "\n",
       "    function on_click_closure(name) {\n",
       "        return function (_event) {\n",
       "            return fig.toolbar_button_onclick(name);\n",
       "        };\n",
       "    }\n",
       "\n",
       "    function on_mouseover_closure(tooltip) {\n",
       "        return function (event) {\n",
       "            if (!event.currentTarget.disabled) {\n",
       "                return fig.toolbar_button_onmouseover(tooltip);\n",
       "            }\n",
       "        };\n",
       "    }\n",
       "\n",
       "    fig.buttons = {};\n",
       "    var buttonGroup = document.createElement('div');\n",
       "    buttonGroup.classList = 'mpl-button-group';\n",
       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) {\n",
       "            /* Instead of a spacer, we start a new button group. */\n",
       "            if (buttonGroup.hasChildNodes()) {\n",
       "                toolbar.appendChild(buttonGroup);\n",
       "            }\n",
       "            buttonGroup = document.createElement('div');\n",
       "            buttonGroup.classList = 'mpl-button-group';\n",
       "            continue;\n",
       "        }\n",
       "\n",
       "        var button = (fig.buttons[name] = document.createElement('button'));\n",
       "        button.classList = 'mpl-widget';\n",
       "        button.setAttribute('role', 'button');\n",
       "        button.setAttribute('aria-disabled', 'false');\n",
       "        button.addEventListener('click', on_click_closure(method_name));\n",
       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
       "\n",
       "        var icon_img = document.createElement('img');\n",
       "        icon_img.src = '_images/' + image + '.png';\n",
       "        icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
       "        icon_img.alt = tooltip;\n",
       "        button.appendChild(icon_img);\n",
       "\n",
       "        buttonGroup.appendChild(button);\n",
       "    }\n",
       "\n",
       "    if (buttonGroup.hasChildNodes()) {\n",
       "        toolbar.appendChild(buttonGroup);\n",
       "    }\n",
       "\n",
       "    var fmt_picker = document.createElement('select');\n",
       "    fmt_picker.classList = 'mpl-widget';\n",
       "    toolbar.appendChild(fmt_picker);\n",
       "    this.format_dropdown = fmt_picker;\n",
       "\n",
       "    for (var ind in mpl.extensions) {\n",
       "        var fmt = mpl.extensions[ind];\n",
       "        var option = document.createElement('option');\n",
       "        option.selected = fmt === mpl.default_extension;\n",
       "        option.innerHTML = fmt;\n",
       "        fmt_picker.appendChild(option);\n",
       "    }\n",
       "\n",
       "    var status_bar = document.createElement('span');\n",
       "    status_bar.classList = 'mpl-message';\n",
       "    toolbar.appendChild(status_bar);\n",
       "    this.message = status_bar;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
       "    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
       "    // which will in turn request a refresh of the image.\n",
       "    this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.send_message = function (type, properties) {\n",
       "    properties['type'] = type;\n",
       "    properties['figure_id'] = this.id;\n",
       "    this.ws.send(JSON.stringify(properties));\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.send_draw_message = function () {\n",
       "    if (!this.waiting) {\n",
       "        this.waiting = true;\n",
       "        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
       "    var format_dropdown = fig.format_dropdown;\n",
       "    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
       "    fig.ondownload(fig, format);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
       "    var size = msg['size'];\n",
       "    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
       "        fig._resize_canvas(size[0], size[1], msg['forward']);\n",
       "        fig.send_message('refresh', {});\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
       "    var x0 = msg['x0'] / fig.ratio;\n",
       "    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
       "    var x1 = msg['x1'] / fig.ratio;\n",
       "    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
       "    x0 = Math.floor(x0) + 0.5;\n",
       "    y0 = Math.floor(y0) + 0.5;\n",
       "    x1 = Math.floor(x1) + 0.5;\n",
       "    y1 = Math.floor(y1) + 0.5;\n",
       "    var min_x = Math.min(x0, x1);\n",
       "    var min_y = Math.min(y0, y1);\n",
       "    var width = Math.abs(x1 - x0);\n",
       "    var height = Math.abs(y1 - y0);\n",
       "\n",
       "    fig.rubberband_context.clearRect(\n",
       "        0,\n",
       "        0,\n",
       "        fig.canvas.width / fig.ratio,\n",
       "        fig.canvas.height / fig.ratio\n",
       "    );\n",
       "\n",
       "    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
       "    // Updates the figure title.\n",
       "    fig.header.textContent = msg['label'];\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
       "    var cursor = msg['cursor'];\n",
       "    switch (cursor) {\n",
       "        case 0:\n",
       "            cursor = 'pointer';\n",
       "            break;\n",
       "        case 1:\n",
       "            cursor = 'default';\n",
       "            break;\n",
       "        case 2:\n",
       "            cursor = 'crosshair';\n",
       "            break;\n",
       "        case 3:\n",
       "            cursor = 'move';\n",
       "            break;\n",
       "    }\n",
       "    fig.rubberband_canvas.style.cursor = cursor;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
       "    fig.message.textContent = msg['message'];\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
       "    // Request the server to send over a new figure.\n",
       "    fig.send_draw_message();\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
       "    fig.image_mode = msg['mode'];\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
       "    for (var key in msg) {\n",
       "        if (!(key in fig.buttons)) {\n",
       "            continue;\n",
       "        }\n",
       "        fig.buttons[key].disabled = !msg[key];\n",
       "        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
       "    if (msg['mode'] === 'PAN') {\n",
       "        fig.buttons['Pan'].classList.add('active');\n",
       "        fig.buttons['Zoom'].classList.remove('active');\n",
       "    } else if (msg['mode'] === 'ZOOM') {\n",
       "        fig.buttons['Pan'].classList.remove('active');\n",
       "        fig.buttons['Zoom'].classList.add('active');\n",
       "    } else {\n",
       "        fig.buttons['Pan'].classList.remove('active');\n",
       "        fig.buttons['Zoom'].classList.remove('active');\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function () {\n",
       "    // Called whenever the canvas gets updated.\n",
       "    this.send_message('ack', {});\n",
       "};\n",
       "\n",
       "// A function to construct a web socket function for onmessage handling.\n",
       "// Called in the figure constructor.\n",
       "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
       "    return function socket_on_message(evt) {\n",
       "        if (evt.data instanceof Blob) {\n",
       "            /* FIXME: We get \"Resource interpreted as Image but\n",
       "             * transferred with MIME type text/plain:\" errors on\n",
       "             * Chrome.  But how to set the MIME type?  It doesn't seem\n",
       "             * to be part of the websocket stream */\n",
       "            evt.data.type = 'image/png';\n",
       "\n",
       "            /* Free the memory for the previous frames */\n",
       "            if (fig.imageObj.src) {\n",
       "                (window.URL || window.webkitURL).revokeObjectURL(\n",
       "                    fig.imageObj.src\n",
       "                );\n",
       "            }\n",
       "\n",
       "            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
       "                evt.data\n",
       "            );\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        } else if (\n",
       "            typeof evt.data === 'string' &&\n",
       "            evt.data.slice(0, 21) === 'data:image/png;base64'\n",
       "        ) {\n",
       "            fig.imageObj.src = evt.data;\n",
       "            fig.updated_canvas_event();\n",
       "            fig.waiting = false;\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        var msg = JSON.parse(evt.data);\n",
       "        var msg_type = msg['type'];\n",
       "\n",
       "        // Call the  \"handle_{type}\" callback, which takes\n",
       "        // the figure and JSON message as its only arguments.\n",
       "        try {\n",
       "            var callback = fig['handle_' + msg_type];\n",
       "        } catch (e) {\n",
       "            console.log(\n",
       "                \"No handler for the '\" + msg_type + \"' message type: \",\n",
       "                msg\n",
       "            );\n",
       "            return;\n",
       "        }\n",
       "\n",
       "        if (callback) {\n",
       "            try {\n",
       "                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
       "                callback(fig, msg);\n",
       "            } catch (e) {\n",
       "                console.log(\n",
       "                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
       "                    e,\n",
       "                    e.stack,\n",
       "                    msg\n",
       "                );\n",
       "            }\n",
       "        }\n",
       "    };\n",
       "};\n",
       "\n",
       "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
       "mpl.findpos = function (e) {\n",
       "    //this section is from http://www.quirksmode.org/js/events_properties.html\n",
       "    var targ;\n",
       "    if (!e) {\n",
       "        e = window.event;\n",
       "    }\n",
       "    if (e.target) {\n",
       "        targ = e.target;\n",
       "    } else if (e.srcElement) {\n",
       "        targ = e.srcElement;\n",
       "    }\n",
       "    if (targ.nodeType === 3) {\n",
       "        // defeat Safari bug\n",
       "        targ = targ.parentNode;\n",
       "    }\n",
       "\n",
       "    // pageX,Y are the mouse positions relative to the document\n",
       "    var boundingRect = targ.getBoundingClientRect();\n",
       "    var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
       "    var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
       "\n",
       "    return { x: x, y: y };\n",
       "};\n",
       "\n",
       "/*\n",
       " * return a copy of an object with only non-object keys\n",
       " * we need this to avoid circular references\n",
       " * http://stackoverflow.com/a/24161582/3208463\n",
       " */\n",
       "function simpleKeys(original) {\n",
       "    return Object.keys(original).reduce(function (obj, key) {\n",
       "        if (typeof original[key] !== 'object') {\n",
       "            obj[key] = original[key];\n",
       "        }\n",
       "        return obj;\n",
       "    }, {});\n",
       "}\n",
       "\n",
       "mpl.figure.prototype.mouse_event = function (event, name) {\n",
       "    var canvas_pos = mpl.findpos(event);\n",
       "\n",
       "    if (name === 'button_press') {\n",
       "        this.canvas.focus();\n",
       "        this.canvas_div.focus();\n",
       "    }\n",
       "\n",
       "    var x = canvas_pos.x * this.ratio;\n",
       "    var y = canvas_pos.y * this.ratio;\n",
       "\n",
       "    this.send_message(name, {\n",
       "        x: x,\n",
       "        y: y,\n",
       "        button: event.button,\n",
       "        step: event.step,\n",
       "        guiEvent: simpleKeys(event),\n",
       "    });\n",
       "\n",
       "    /* This prevents the web browser from automatically changing to\n",
       "     * the text insertion cursor when the button is pressed.  We want\n",
       "     * to control all of the cursor setting manually through the\n",
       "     * 'cursor' event from matplotlib */\n",
       "    event.preventDefault();\n",
       "    return false;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
       "    // Handle any extra behaviour associated with a key event\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.key_event = function (event, name) {\n",
       "    // Prevent repeat events\n",
       "    if (name === 'key_press') {\n",
       "        if (event.which === this._key) {\n",
       "            return;\n",
       "        } else {\n",
       "            this._key = event.which;\n",
       "        }\n",
       "    }\n",
       "    if (name === 'key_release') {\n",
       "        this._key = null;\n",
       "    }\n",
       "\n",
       "    var value = '';\n",
       "    if (event.ctrlKey && event.which !== 17) {\n",
       "        value += 'ctrl+';\n",
       "    }\n",
       "    if (event.altKey && event.which !== 18) {\n",
       "        value += 'alt+';\n",
       "    }\n",
       "    if (event.shiftKey && event.which !== 16) {\n",
       "        value += 'shift+';\n",
       "    }\n",
       "\n",
       "    value += 'k';\n",
       "    value += event.which.toString();\n",
       "\n",
       "    this._key_event_extra(event, name);\n",
       "\n",
       "    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
       "    return false;\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
       "    if (name === 'download') {\n",
       "        this.handle_save(this, null);\n",
       "    } else {\n",
       "        this.send_message('toolbar_button', { name: name });\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
       "    this.message.textContent = tooltip;\n",
       "};\n",
       "\n",
       "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
       "// prettier-ignore\n",
       "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
       "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
       "\n",
       "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
       "\n",
       "mpl.default_extension = \"png\";/* global mpl */\n",
       "\n",
       "var comm_websocket_adapter = function (comm) {\n",
       "    // Create a \"websocket\"-like object which calls the given IPython comm\n",
       "    // object with the appropriate methods. Currently this is a non binary\n",
       "    // socket, so there is still some room for performance tuning.\n",
       "    var ws = {};\n",
       "\n",
       "    ws.close = function () {\n",
       "        comm.close();\n",
       "    };\n",
       "    ws.send = function (m) {\n",
       "        //console.log('sending', m);\n",
       "        comm.send(m);\n",
       "    };\n",
       "    // Register the callback with on_msg.\n",
       "    comm.on_msg(function (msg) {\n",
       "        //console.log('receiving', msg['content']['data'], msg);\n",
       "        // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
       "        ws.onmessage(msg['content']['data']);\n",
       "    });\n",
       "    return ws;\n",
       "};\n",
       "\n",
       "mpl.mpl_figure_comm = function (comm, msg) {\n",
       "    // This is the function which gets called when the mpl process\n",
       "    // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
       "\n",
       "    var id = msg.content.data.id;\n",
       "    // Get hold of the div created by the display call when the Comm\n",
       "    // socket was opened in Python.\n",
       "    var element = document.getElementById(id);\n",
       "    var ws_proxy = comm_websocket_adapter(comm);\n",
       "\n",
       "    function ondownload(figure, _format) {\n",
       "        window.open(figure.canvas.toDataURL());\n",
       "    }\n",
       "\n",
       "    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
       "\n",
       "    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
       "    // web socket which is closed, not our websocket->open comm proxy.\n",
       "    ws_proxy.onopen();\n",
       "\n",
       "    fig.parent_element = element;\n",
       "    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
       "    if (!fig.cell_info) {\n",
       "        console.error('Failed to find cell for figure', id, fig);\n",
       "        return;\n",
       "    }\n",
       "    fig.cell_info[0].output_area.element.on(\n",
       "        'cleared',\n",
       "        { fig: fig },\n",
       "        fig._remove_fig_handler\n",
       "    );\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
       "    var width = fig.canvas.width / fig.ratio;\n",
       "    fig.cell_info[0].output_area.element.off(\n",
       "        'cleared',\n",
       "        fig._remove_fig_handler\n",
       "    );\n",
       "    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
       "\n",
       "    // Update the output cell to use the data from the current canvas.\n",
       "    fig.push_to_output();\n",
       "    var dataURL = fig.canvas.toDataURL();\n",
       "    // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
       "    // the notebook keyboard shortcuts fail.\n",
       "    IPython.keyboard_manager.enable();\n",
       "    fig.parent_element.innerHTML =\n",
       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
       "    fig.close_ws(fig, msg);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
       "    fig.send_message('closing', msg);\n",
       "    // fig.ws.close()\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
       "    // Turn the data on the canvas into data in the output cell.\n",
       "    var width = this.canvas.width / this.ratio;\n",
       "    var dataURL = this.canvas.toDataURL();\n",
       "    this.cell_info[1]['text/html'] =\n",
       "        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.updated_canvas_event = function () {\n",
       "    // Tell IPython that the notebook contents must change.\n",
       "    IPython.notebook.set_dirty(true);\n",
       "    this.send_message('ack', {});\n",
       "    var fig = this;\n",
       "    // Wait a second, then push the new image to the DOM so\n",
       "    // that it is saved nicely (might be nice to debounce this).\n",
       "    setTimeout(function () {\n",
       "        fig.push_to_output();\n",
       "    }, 1000);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._init_toolbar = function () {\n",
       "    var fig = this;\n",
       "\n",
       "    var toolbar = document.createElement('div');\n",
       "    toolbar.classList = 'btn-toolbar';\n",
       "    this.root.appendChild(toolbar);\n",
       "\n",
       "    function on_click_closure(name) {\n",
       "        return function (_event) {\n",
       "            return fig.toolbar_button_onclick(name);\n",
       "        };\n",
       "    }\n",
       "\n",
       "    function on_mouseover_closure(tooltip) {\n",
       "        return function (event) {\n",
       "            if (!event.currentTarget.disabled) {\n",
       "                return fig.toolbar_button_onmouseover(tooltip);\n",
       "            }\n",
       "        };\n",
       "    }\n",
       "\n",
       "    fig.buttons = {};\n",
       "    var buttonGroup = document.createElement('div');\n",
       "    buttonGroup.classList = 'btn-group';\n",
       "    var button;\n",
       "    for (var toolbar_ind in mpl.toolbar_items) {\n",
       "        var name = mpl.toolbar_items[toolbar_ind][0];\n",
       "        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
       "        var image = mpl.toolbar_items[toolbar_ind][2];\n",
       "        var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
       "\n",
       "        if (!name) {\n",
       "            /* Instead of a spacer, we start a new button group. */\n",
       "            if (buttonGroup.hasChildNodes()) {\n",
       "                toolbar.appendChild(buttonGroup);\n",
       "            }\n",
       "            buttonGroup = document.createElement('div');\n",
       "            buttonGroup.classList = 'btn-group';\n",
       "            continue;\n",
       "        }\n",
       "\n",
       "        button = fig.buttons[name] = document.createElement('button');\n",
       "        button.classList = 'btn btn-default';\n",
       "        button.href = '#';\n",
       "        button.title = name;\n",
       "        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n",
       "        button.addEventListener('click', on_click_closure(method_name));\n",
       "        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
       "        buttonGroup.appendChild(button);\n",
       "    }\n",
       "\n",
       "    if (buttonGroup.hasChildNodes()) {\n",
       "        toolbar.appendChild(buttonGroup);\n",
       "    }\n",
       "\n",
       "    // Add the status bar.\n",
       "    var status_bar = document.createElement('span');\n",
       "    status_bar.classList = 'mpl-message pull-right';\n",
       "    toolbar.appendChild(status_bar);\n",
       "    this.message = status_bar;\n",
       "\n",
       "    // Add the close button to the window.\n",
       "    var buttongrp = document.createElement('div');\n",
       "    buttongrp.classList = 'btn-group inline pull-right';\n",
       "    button = document.createElement('button');\n",
       "    button.classList = 'btn btn-mini btn-primary';\n",
       "    button.href = '#';\n",
       "    button.title = 'Stop Interaction';\n",
       "    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n",
       "    button.addEventListener('click', function (_evt) {\n",
       "        fig.handle_close(fig, {});\n",
       "    });\n",
       "    button.addEventListener(\n",
       "        'mouseover',\n",
       "        on_mouseover_closure('Stop Interaction')\n",
       "    );\n",
       "    buttongrp.appendChild(button);\n",
       "    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
       "    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
       "    var fig = event.data.fig;\n",
       "    if (event.target !== this) {\n",
       "        // Ignore bubbled events from children.\n",
       "        return;\n",
       "    }\n",
       "    fig.close_ws(fig, {});\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._root_extra_style = function (el) {\n",
       "    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
       "    // this is important to make the div 'focusable\n",
       "    el.setAttribute('tabindex', 0);\n",
       "    // reach out to IPython and tell the keyboard manager to turn it's self\n",
       "    // off when our div gets focus\n",
       "\n",
       "    // location in version 3\n",
       "    if (IPython.notebook.keyboard_manager) {\n",
       "        IPython.notebook.keyboard_manager.register_events(el);\n",
       "    } else {\n",
       "        // location in version 2\n",
       "        IPython.keyboard_manager.register_events(el);\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
       "    var manager = IPython.notebook.keyboard_manager;\n",
       "    if (!manager) {\n",
       "        manager = IPython.keyboard_manager;\n",
       "    }\n",
       "\n",
       "    // Check for shift+enter\n",
       "    if (event.shiftKey && event.which === 13) {\n",
       "        this.canvas_div.blur();\n",
       "        // select the cell after this one\n",
       "        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
       "        IPython.notebook.select(index + 1);\n",
       "    }\n",
       "};\n",
       "\n",
       "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
       "    fig.ondownload(fig, null);\n",
       "};\n",
       "\n",
       "mpl.find_output_cell = function (html_output) {\n",
       "    // Return the cell and output element which can be found *uniquely* in the notebook.\n",
       "    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
       "    // IPython event is triggered only after the cells have been serialised, which for\n",
       "    // our purposes (turning an active figure into a static one), is too late.\n",
       "    var cells = IPython.notebook.get_cells();\n",
       "    var ncells = cells.length;\n",
       "    for (var i = 0; i < ncells; i++) {\n",
       "        var cell = cells[i];\n",
       "        if (cell.cell_type === 'code') {\n",
       "            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
       "                var data = cell.output_area.outputs[j];\n",
       "                if (data.data) {\n",
       "                    // IPython >= 3 moved mimebundle to data attribute of output\n",
       "                    data = data.data;\n",
       "                }\n",
       "                if (data['text/html'] === html_output) {\n",
       "                    return [cell, data, j];\n",
       "                }\n",
       "            }\n",
       "        }\n",
       "    }\n",
       "};\n",
       "\n",
       "// Register the function which deals with the matplotlib target/channel.\n",
       "// The kernel may be null if the page has been refreshed.\n",
       "if (IPython.notebook.kernel !== null) {\n",
       "    IPython.notebook.kernel.comm_manager.register_target(\n",
       "        'matplotlib',\n",
       "        mpl.mpl_figure_comm\n",
       "    );\n",
       "}\n"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<img src=\"\" width=\"640\">"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.animation as animation\n",
    "\n",
    "%matplotlib notebook\n",
    "\n",
    "start = -10\n",
    "end = 10\n",
    "W = np.array([5, 4])\n",
    "X, y = createTrainDatas(W, start, end, size=200)\n",
    "x1 = X[y>0][:, 0]\n",
    "y1 = X[y>0][:, 1]\n",
    "x2 = X[y<0][:, 0]\n",
    "y2 = X[y<0][:, 1]\n",
    "\n",
    "Ws, errors = plaReturn(X, y)\n",
    "fig = plt.figure()\n",
    "plt.title('PLA')\n",
    "p1 = plt.scatter(x1, y1, c='b', marker='o', s=20)\n",
    "p2 = plt.scatter(x2, y2, c='r', marker='o', s=20)\n",
    "line, = plt.plot(0, 0, markersize=0, label='W')\n",
    "error, = plt.plot(0, 0, c='k', marker='x', markersize=0)\n",
    "\n",
    "ani = animation.FuncAnimation(fig, update, range(0, len(Ws)), interval=1000, blit=True, repeat=False)\n",
    "ani.save('pla.gif')\n",
    "plt.legend(loc=\"upper right\")\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
